/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "ogl_font.hh"
#include "file_mng.hh"

#include <string.h>
#include <malloc.h>

#include <GL/gl.h>
#define GLX_GLXEXT_PROTOTYPES
#include <GL/glx.h>
#include <GL/glu.h>

CLASS_INIT(ogl_font); // OGL_FONT CLASS

// DEFINES
#define OGL_FONT_LIST_MARK 0xffffffff
#define OGL_FONT_DEF_FONT  "/share/introsight/fonts/blender.ttf"
#define OGL_FONT_DEF_XFONT "6x13"

ogl_font::ogl_font()
{
    CLASS_CTOR(ogl_font);
    ctor();
}

ogl_font::~ogl_font()
{
    CLASS_DTOR(ogl_font);
    dtor();
}

void ogl_font::ctor()
{
    // init variables
    name=NULL;
    height=32;
    width=0;
    free_type=false;

    list_base=OGL_FONT_LIST_MARK;
    textures=NULL;
}

void ogl_font::dtor()
{
    if(name) name=(char*)realloc(name, 0);
    clean_textures();
    clean_lists();
}

void ogl_font::clean_lists()
{
    if(list_base!=OGL_FONT_LIST_MARK)
    {
	glDeleteLists(list_base, OGL_FONT_N_CHAR);
	list_base=OGL_FONT_LIST_MARK;
    }
}

void ogl_font::clean_textures()
{
    if(textures)
    {
	glDeleteTextures(OGL_FONT_N_CHAR, textures);
	textures=(unsigned int *)realloc(textures, 0);
    }
}

bool ogl_font::init(const char *font_name)
{
    if(name) name=(char*)realloc(name, 0);
    if(font_name) name=strdup(font_name);
    free_type=false;
    return init();
}

bool ogl_font::init(const char *font_filename, float height_)
{
    if(name) name=(char*)realloc(name, 0);
    if(font_filename) name=strdup(font_filename);
    height=height_;
    free_type=true;
    return init();
}

bool ogl_font::init()
{
    Display *dpy;
    if(!(dpy=glXGetCurrentDisplay()) || !glXGetCurrentContext())
	ERROR_RX(false, "OpenGL context required at ogl_font::init");

    if(free_type)
	return init_free_type();
    else
	return init_bitmap(dpy);
}

bool ogl_font::init_bitmap(Display *dpy)
{
    if(!name) name=strdup(OGL_FONT_DEF_XFONT);

    XFontStruct *font;
    if(!(font=XLoadQueryFont(dpy, name)))
	ERROR_RX(false, "XLoadQueryFont {%s}", name);

    clean_lists();
    list_base=glGenLists(OGL_FONT_N_CHAR);
    glRasterPos2d(-1e300, -1e300);
    glXUseXFont(font->fid, 0, OGL_FONT_N_CHAR, list_base);

    // calculate height of font
    width=(float)font->max_bounds.width;
    height=(float)(font->max_bounds.ascent+font->max_bounds.descent);

    XFreeFont(dpy, font);
    return true;
}

bool ogl_font::init_free_type()
{
    if(!name) name=strdup(STR(PREFIX) OGL_FONT_DEF_FONT);

    // check filename
    while(1)
    {
	if(!file_mng::regular(name))
	{   // does not exists
	    char *f2=strdup(STR(PREFIX) OGL_FONT_DEF_FONT);
	    int error=0;
	    if(!strcmp(name, f2)) error++; // filename and f2, same file
	    free(name);
	    name=f2;
	    if(error) ERROR_RX(false, "Missing font {%s}", name);
	    ERROR("Warning: backing to default font {%s}", f2);
	}
	else break; // ok, filename exists
    }

    FT_Library library;
    if(FT_Init_FreeType(&library)) ERROR_RX(false, "FT_Init_FreeType");

    // The object in which Freetype holds information on a given
    // font is called a "face".
    FT_Face face;

    // This is where we load in the font information from the file.
    // Of all the places where the code might die, this is the most likely,
    // as FT_New_Face will die if the font file does not exist or is somehow broken.
    if(FT_New_Face(library, name, 0, &face)) ERROR_RX(false, "FT_New_Face");

    // For some twisted reason, Freetype measures font size
    // in terms of 1/64ths of pixels.
    FT_Set_Char_Size(face, (unsigned int)(height*64), (unsigned int)(height*64), 96, 96);

    // Here we ask opengl to allocate resources for
    // all the textures and displays lists which we
    // are about to create.
    clean_textures();
    clean_lists();
    textures=(unsigned int *)realloc(textures, OGL_FONT_N_CHAR*sizeof(unsigned int));

    list_base=glGenLists(OGL_FONT_N_CHAR);
    glGenTextures(OGL_FONT_N_CHAR, textures);

    if(glGetError()==GL_NO_ERROR)
    {
	// This is where we actually create each of the fonts display lists.
	for(unsigned char i=0; i<OGL_FONT_N_CHAR; i++)
	    if(!make_dlist(face, i)) ERROR_RX(false,"Incomplete font resources");
    }
    else ERROR("glGenLists, glGenTextures");

    // We don't need the face information now that the display
    // lists have been created, so we free the assosiated resources.
    FT_Done_Face(face);

    // Ditto for the library.
    FT_Done_FreeType(library);

    return true;
}

bool ogl_font::make_dlist(FT_Face face, char ch)
{
    // reset params
    letter_params[(int)(unsigned char)ch][0]=letter_params[(int)(unsigned char)ch][1]=letter_params[(int)(unsigned char)ch][2]=0;

    // The first thing we do is get FreeType to render our character
    // into a bitmap.  This actually requires a couple of FreeType commands:

    // Load the Glyph for our character.
    if(FT_Load_Glyph(face, FT_Get_Char_Index(face, ch), FT_LOAD_DEFAULT)) ERROR_RX(false, "FT_Load_Glyph");

    // Move the face's glyph into a Glyph object.
    FT_Glyph glyph;
    if(FT_Get_Glyph(face->glyph, &glyph)) ERROR_RX(false, "FT_Get_Glyph");

    // Convert the glyph to a bitmap.
    FT_Glyph_To_Bitmap(&glyph, ft_render_mode_normal, 0, 1);
    FT_BitmapGlyph bitmap_glyph=(FT_BitmapGlyph)glyph;

    // This reference will make accessing the bitmap easier
    FT_Bitmap &bitmap=bitmap_glyph->bitmap;

    // Use our helper function to get the widths of
    // the bitmap data that we will need in order to create
    // our texture.
    int width = next_p2(bitmap.width+1);
    int height = next_p2(bitmap.rows+1);

    // Allocate memory for the texture data.
    unsigned char *expanded_data = new unsigned char[ 2 * width * height];

    // Here we fill in the data for the expanded bitmap.
    // Notice that we are using two channel bitmap (one for
    // luminocity and one for alpha), but we assign
    // both luminocity and alpha to the value that we
    // find in the FreeType bitmap.
    // We use the ?: operator so that value which we use
    // will be 0 if we are in the padding zone, and whatever
    // is the the Freetype bitmap otherwise.
    for(int j=0; j<height; j++)
	for(int i=0; i<width; i++)
	    expanded_data[2*(i+j*width)]=expanded_data[2*(i+j*width)+1] =
		(i>=bitmap.width || j>=bitmap.rows) ?
	        0 : bitmap.buffer[i + bitmap.width*j];

    // Now we just setup some texture paramaters.
    glBindTexture(GL_TEXTURE_2D, textures[(unsigned)ch]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    // Here we actually create the texture itself, notice
    // that we are using GL_LUMINANCE_ALPHA to indicate that
    // we are using 2 channel data.
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
		 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data);

    // With the texture created, we don't need to expanded data anymore
    delete []expanded_data;

    // So now we can create the display list
    glNewList(list_base+ch, GL_COMPILE);

    glBindTexture(GL_TEXTURE_2D, textures[(unsigned)ch]);

    // first we need to move over a little so that
    // the character has the right amount of space
    // between it and the one before it.
    glTranslatef(bitmap_glyph->left, 0, 0);

    // Now we move down a little in the case that the
    // bitmap extends past the bottom of the line
    // (this is only true for characters like 'g' or 'y'.
    if(bitmap_glyph->top-bitmap.rows)
    {
	glPushMatrix();
	glTranslatef(0, bitmap_glyph->top-bitmap.rows, 0);
    }

    // Now we need to account for the fact that many of
    // our textures are filled with empty padding space.
    // We figure what portion of the texture is used by
    // the actual character and store that information in
    // the x and y variables, then when we draw the
    // quad, we will only reference the parts of the texture
    // that we contain the character itself.
    float x=(float)bitmap.width / (float)width, y=(float)bitmap.rows / (float)height;

    // Here we draw the texturemaped quads.
    // The bitmap that we got from FreeType was not
    // oriented quite like we would like it to be,
    // so we need to link the texture to the quad
    // so that the result will be properly aligned.
    glBegin(GL_QUADS);
    glTexCoord2d(0, 0); glVertex2f(0, bitmap.rows);
    glTexCoord2d(0, y); glVertex2f(0, 0);
    glTexCoord2d(x, y); glVertex2f(bitmap.width, 0);
    glTexCoord2d(x, 0); glVertex2f(bitmap.width, bitmap.rows);
    glEnd();
    if(bitmap_glyph->top-bitmap.rows) glPopMatrix();
    glTranslatef(face->glyph->advance.x >> 6, 0, 0);

    // increment the raster position as if we were a bitmap font.
    // (only needed if you want to calculate text length)
    // glBitmap(0, 0, 0, 0, face->glyph->advance.x >> 6, 0, NULL);

    // Finnish the display list
    glEndList();

    // save params
    letter_params[(int)(unsigned char)ch][0]=(face->glyph->advance.x >> 6) + bitmap_glyph->left;
    letter_params[(int)(unsigned char)ch][1]=bitmap_glyph->top;
    letter_params[(int)(unsigned char)ch][2]=-(bitmap_glyph->top-bitmap.rows);

    // free glyph
    FT_Done_Glyph(glyph);

    return true;
}

void ogl_font::write(const char *fmt, ...)
{
    char text[OGL_MAX_TEXT];
    va_list ap;

    if(fmt==NULL) *text=0;
    else
    {
	va_start(ap, fmt);
	vsnprintf(text, OGL_MAX_TEXT, fmt, ap);
	va_end(ap);
    }

    if(free_type)
    {
	glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT  | GL_ENABLE_BIT | GL_TRANSFORM_BIT);
	glDisable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }

    glListBase(list_base);

    // The commented out raster position stuff can be useful if you need to
    // know the length of the text that you are creating.
    // If you decide to use it make sure to also uncomment the glBitmap command
    // in make_dlist().
    // glRasterPos2f(0, 0);
    glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
    // float rpos[4];
    // glGetFloatv(GL_CURRENT_RASTER_POSITION, rpos);
    // float len=x-rpos[0];

    if(free_type) glPopAttrib();
}

void ogl_font::size(float dims[3], const char *fmt, ...)
{
    char text[OGL_MAX_TEXT];
    va_list ap;

    if(fmt==NULL) *text=0;
    else
    {
	va_start(ap, fmt);
	vsnprintf(text, OGL_MAX_TEXT, fmt, ap);
	va_end(ap);
    }

    int n_text=strlen(text);
    dims[0]=dims[1]=dims[2]=0;
    for(int i=0; i<n_text; i++)
    {
	float *letter=letter_params[(int)(unsigned char)text[i]];
	dims[0]+=letter[0];
	if(letter[1]>dims[1]) dims[1]=letter[1];
	if(letter[2]>dims[2]) dims[2]=letter[2];
    }
}

float ogl_font::get_scale_font(float h)
{
    return h/height;
}

float ogl_font::scale_font(float h)
{
    float x=h/height;
    glScalef(x, x, x);
    return x;
}
